package ldh.im.rpc.connection.executor;

import ldh.im.rpc.config.RpcConfig;
import ldh.im.rpc.config.RpcMapperInfo;
import ldh.im.rpc.connection.RpcReadChannel;
import ldh.im.rpc.connection.handle.RpcContext;
import ldh.im.rpc.dto.RpcData;
import ldh.im.rpc.dto.RpcExtData;
import ldh.im.rpc.exception.RpcErrorCode;
import ldh.im.rpc.exception.RpcException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class DefaultRpcRouterExecutor implements RpcRouterExecutor {

    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRpcRouterExecutor.class);

    private Map<Class, Object> cacheMap = new ConcurrentHashMap<>();
    private RpcConfig rpcConfig = null;

    public DefaultRpcRouterExecutor(RpcConfig rpcConfig) {
        this.rpcConfig = rpcConfig;
    }

    @Override
    public <T> T executor(String path, RpcContext context, Object param) {
        RpcMapperInfo rpcMapperInfo = rpcConfig.getRouter(path);
        String module = rpcConfig.getName();
        if (rpcMapperInfo == null) {
            throw new RpcException(RpcErrorCode.No_router, module, path);
        }
        return doHandle(context, rpcMapperInfo, param);
    }

    @Override
    public void asyncExecutor(boolean mustResult, String path, RpcContext context, Object param) {
        rpcConfig.getBusinessExecutorService().submit(()->{
            try {
                Object object = executor(path, context, param);
                if(mustResult) {
                    writeResult(context.getSession().getRpcChannel(), context.getRpcData(), object);
                }
            } catch (Exception e) {
                LOGGER.error("异步处理业务失败", e);
            }
        });
    }

    private void writeResult(RpcReadChannel channel, RpcData rpcData, Object resultObj) {
        RpcData resultRpcData = new RpcData();
        resultRpcData.setStatus(100);  // 成功
        resultRpcData.setPath(rpcData.getPath());
        resultRpcData.setVersion(1);
        resultRpcData.setAuthCode(0);
        RpcExtData rpcExtData = new RpcExtData();
        rpcExtData.getExtDataMap().put(RpcExtData.LOCAL_SEQ, rpcData.getExtData().getExtDataMap().get(RpcExtData.LOCAL_SEQ));
        resultRpcData.setExtData(rpcExtData);
        resultRpcData.setData(resultObj);

        // todo
//        channel.write(rpcData);
    }

    private <T>T doHandle(RpcContext context, RpcMapperInfo rpcMapper, Object param) throws RpcException {
        T result = null;
        Method method = rpcMapper.getMethod();
        Object routerObj = getCacheObject(rpcMapper.getClazz());
        Object[] params = buildParams(context, method.getParameterTypes(), param);
        try {
            result = (T) method.invoke(routerObj, params);
        } catch (Exception e) {
            LOGGER.error("handle router error, router {}, method {}, error {}", rpcMapper.getClazz().getName(), rpcMapper.getMethod().getName(), e.getMessage());
            String msg = e.getMessage();
            if (e instanceof InvocationTargetException) {
                InvocationTargetException invocationTargetException = (InvocationTargetException) e;
                msg = invocationTargetException.getTargetException().getMessage();
                if (invocationTargetException.getCause() instanceof RpcException) {
                    throw (RpcException)invocationTargetException.getCause();
                }
                throw new RpcException(RpcErrorCode.Business_error, invocationTargetException.getTargetException());
            }
            throw new RpcException(RpcErrorCode.Business_error, e, msg);
        }
        return result;
    }

    private Object[] buildParams(RpcContext context, Class<?>[] parameterTypes, Object param) {
        Object[] objects = new Object[parameterTypes.length];
        int i = 0;
        int paramNum = 0;
        for (Class clazz : parameterTypes) {
            if (clazz.isInstance(param)) {
                objects[i++] = param;
                paramNum++;
                continue;
            } else if (clazz.isInstance(context)) {
                objects[i++] = context;
                paramNum++;
                continue;
            } else if (clazz.isInstance(context.getSession())) {
                objects[i++] = context.getSession();
                paramNum++;
                continue;
            }
            Object obj = context.get(clazz.getName());
            if (obj != null) {
                objects[i++] = obj;
                paramNum++;
                continue;
            }
            if (param == null) {
                objects[i++] = param;
                paramNum++;
            }
            for (Object value : context.values()) {
                if (clazz.isInstance(value)) {
                    objects[i++] = obj;
                    paramNum++;
                    continue;
                }
            }
            obj = rpcConfig.getRouterParamBean(clazz);
            if (obj != null) {
                objects[i++] = obj;
                paramNum++;
                continue;
            }
        }
        if (paramNum != parameterTypes.length) {
            throw new RpcException(RpcErrorCode.Param_failure, parameterTypes[paramNum]);
        }
        return objects;
    }

    private Object getCacheObject(Class clazz) {
        if (cacheMap.containsKey(clazz)) {
            return cacheMap.get(clazz);
        }
        Object obj = newClazz(clazz);
        cacheMap.putIfAbsent(clazz, obj);
        return cacheMap.get(clazz);
    }

    private <T>T newClazz(Class clazz) {
        return (T) rpcConfig.getRouter(clazz);
    }
}
